﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
using Volo.Abp.Validation;
using IdentityUser = Volo.Abp.Identity.IdentityUser;

namespace Volo.Abp.Account.Public.Web.Pages.Account
{
	[DisableAuditing]
	public class LoginModel : AccountPageModel
	{
		[BindProperty(SupportsGet = true)]
		[HiddenInput]
		public string ReturnUrl { get; set; }

		[BindProperty(SupportsGet = true)]
		[HiddenInput]
		public string ReturnUrlHash { get; set; }

		[BindProperty]
		public LoginModel.LoginInputModel LoginInput { get; set; }

		public bool EnableLocalLogin { get; set; }

		public bool IsSelfRegistrationEnabled { get; set; }

		public IEnumerable<LoginModel.ExternalProviderModel> ExternalProviders { get; set; }

		public IEnumerable<LoginModel.ExternalProviderModel> VisibleExternalProviders
		{
			get
			{
				return from x in this.ExternalProviders
				where !string.IsNullOrWhiteSpace(x.DisplayName)
				select x;
			}
		}

		public bool IsExternalLoginOnly
		{
			get
			{
				if (!this.EnableLocalLogin)
				{
					var externalProviders = this.ExternalProviders;
					return externalProviders != null && externalProviders.Count<LoginModel.ExternalProviderModel>() == 1;
				}
				return false;
			}
		}

		public string ExternalLoginScheme
		{
			get
			{
				if (!this.IsExternalLoginOnly)
				{
					return null;
				}
				var externalProviders = this.ExternalProviders;
				if (externalProviders == null)
				{
					return null;
				}
				var externalProviderModel = externalProviders.SingleOrDefault<LoginModel.ExternalProviderModel>();
				if (externalProviderModel == null)
				{
					return null;
				}
				return externalProviderModel.AuthenticationScheme;
			}
		}

		public LoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions)
		{
			this.SchemeProvider = schemeProvider;
			this.AccountOptions = accountOptions.Value;
		}

		public virtual async Task OnGetAsync()
		{
			this.LoginInput = new LoginModel.LoginInputModel();
			var externalProviders = await this.GetExternalProviders();
			this.ExternalProviders = externalProviders;
			this.EnableLocalLogin = await base.SettingProvider.IsTrueAsync("Abp.Account.EnableLocalLogin");
			this.IsSelfRegistrationEnabled = await base.SettingProvider.IsTrueAsync("Abp.Account.IsSelfRegistrationEnabled");
			if (this.IsExternalLoginOnly)
			{
				throw new NotImplementedException();
			}
		}

		[UnitOfWork]
		public virtual async Task<IActionResult> OnPostAsync(string action)
		{
			this.ValidateModel();
			var externalProviders = await this.GetExternalProviders();
			this.ExternalProviders = externalProviders;
			this.EnableLocalLogin = await base.SettingProvider.IsTrueAsync("Abp.Account.EnableLocalLogin");
			this.IsSelfRegistrationEnabled = await base.SettingProvider.IsTrueAsync("Abp.Account.IsSelfRegistrationEnabled");
			await this.ReplaceEmailToUsernameOfInputIfNeeds();
			var signInResult = await base.SignInManager.PasswordSignInAsync(this.LoginInput.UserNameOrEmailAddress, this.LoginInput.Password, this.LoginInput.RememberMe, true);
			IActionResult result;
			if (signInResult.RequiresTwoFactor)
			{
				result = this.RedirectToPage("./SendSecurityCode", new
				{
					returnUrl = this.ReturnUrl,
					returnUrlHash = this.ReturnUrlHash,
					rememberMe = this.LoginInput.RememberMe
				});
			}
			else if (signInResult.IsLockedOut)
			{
				base.Alerts.Warning(base.L["UserLockedOutMessage"]);
				result = this.Page();
			}
			else if (signInResult.IsNotAllowed)
			{
				base.Alerts.Warning(base.L["LoginIsNotAllowed"]);
				result = this.Page();
			}
			else if (!signInResult.Succeeded)
			{
				base.Alerts.Danger(base.L["InvalidUserNameOrPassword"]);
				result = this.Page();
			}
			else
			{
				var user = await base.UserManager.FindByNameAsync(this.LoginInput.UserNameOrEmailAddress);
				if (user == null)
				{
					var identityUser = await base.UserManager.FindByEmailAsync(this.LoginInput.UserNameOrEmailAddress);
				}
				result = base.RedirectSafely(this.ReturnUrl, this.ReturnUrlHash);
			}
			return result;
		}

		protected virtual async Task<List<LoginModel.ExternalProviderModel>> GetExternalProviders()
		{
			var source = await this.SchemeProvider.GetAllSchemesAsync();
			return (from x in source.Where(scheme => (scheme.DisplayName != null || scheme.Name.Equals(this.AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)))
					select new LoginModel.ExternalProviderModel
					{
						DisplayName = x.DisplayName,
						AuthenticationScheme = x.Name
					}).ToList<LoginModel.ExternalProviderModel>();
		}

		protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds()
		{
			if (ValidationHelper.IsValidEmailAddress(this.LoginInput.UserNameOrEmailAddress))
			{
				var identityUser = await base.UserManager.FindByNameAsync(this.LoginInput.UserNameOrEmailAddress);
				if (identityUser == null)
				{
					var result = await base.UserManager.FindByEmailAsync(this.LoginInput.UserNameOrEmailAddress);
					if (result != null)
					{
						this.LoginInput.UserNameOrEmailAddress = result.UserName;
					}
				}
			}
		}

		[UnitOfWork]
		public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
		{
			string redirectUrl = base.Url.Page("./Login", "ExternalLoginCallback", new
			{
				this.ReturnUrl,
				this.ReturnUrlHash
			});
			var authenticationProperties = base.SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
			authenticationProperties.Items["scheme"] = provider;
			return await Task.FromResult<ChallengeResult>(this.Challenge(authenticationProperties, new string[]
			{
				provider
			}));
		}

		[UnitOfWork]
		public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
		{
			IActionResult result;
			if (remoteError != null)
			{
				base.Logger.LogWarning("External login callback error: " + remoteError, Array.Empty<object>());
				result = this.RedirectToPage("./Login");
			}
			else
			{
				var externalLoginInfo = await base.SignInManager.GetExternalLoginInfoAsync(null);
				if (externalLoginInfo == null)
				{
					base.Logger.LogWarning("External login info is not available", Array.Empty<object>());
					result = this.RedirectToPage("./Login");
				}
				else
				{
					var signInResult = await base.SignInManager.ExternalLoginSignInAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, true, true);
					if (signInResult.IsLockedOut)
					{
						throw new UserFriendlyException("Cannot proceed because user is locked out!");
					}
					if (signInResult.Succeeded)
					{
						result = base.RedirectSafely(returnUrl, returnUrlHash);
					}
					else
					{
						externalLoginInfo = await base.SignInManager.GetExternalLoginInfoAsync(null);
						if (externalLoginInfo == null)
						{
							throw new ApplicationException("Error loading external login information during confirmation.");
						}
						if (externalLoginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) == null)
						{
							result = this.RedirectToPage("./Register", new
							{
								IsExternalLogin = true,
								ExternalLoginAuthSchema = externalLoginInfo.LoginProvider,
								ReturnUrl = returnUrl
							});
						}
						else
						{
							var user = await this.CreateExternalUserAsync(externalLoginInfo);
							await base.SignInManager.SignInAsync(user, false, null);
							result = base.RedirectSafely(returnUrl, returnUrlHash);
						}
					}
				}
			}
			return result;
		}

		protected virtual async Task<IdentityUser> CreateExternalUserAsync(ExternalLoginInfo info)
		{
			string emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email);
			var user = new IdentityUser(base.GuidGenerator.Create(), emailAddress, emailAddress, base.CurrentTenant.Id);
			var identityResult = await base.UserManager.CreateAsync(user);
			identityResult.CheckErrors();
			AbpIdentityResultExtensions.CheckErrors(await base.UserManager.SetEmailAsync(user, emailAddress));
			AbpIdentityResultExtensions.CheckErrors(await base.UserManager.AddLoginAsync(user, info));
			AbpIdentityResultExtensions.CheckErrors(await base.UserManager.AddDefaultRolesAsync(user));
			return user;
		}

		protected readonly IAuthenticationSchemeProvider SchemeProvider;

		protected readonly AbpAccountOptions AccountOptions;

		public class LoginInputModel
		{
			[DynamicStringLength(typeof(IdentityUserConsts), "MaxEmailLength", null)]
			[Required]
			public string UserNameOrEmailAddress { get; set; }

			[DisableAuditing]
			[DataType(DataType.Password)]
			[DynamicStringLength(typeof(IdentityUserConsts), "MaxPasswordLength", null)]
			[Required]
			public string Password { get; set; }

			public bool RememberMe { get; set; }
		}

		public class ExternalProviderModel
		{
			public string DisplayName { get; set; }

			public string AuthenticationScheme { get; set; }
		}
	}
}
